package tictactoe.multiplayer.server;

//~--- non-JDK imports --------------------------------------------------------

import tictactoe.Player;

import tictactoe.exceptions.network.NetworkException;

import tictactoe.util.Constants;

//~--- JDK imports ------------------------------------------------------------

import java.io.IOException;

import java.net.ServerSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;

import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JOptionPane;

/**
 *
 * @author Home
 */
public class Server {
    private static final int   SOCKET_TO = 100;
    private static final int   PORT      = Constants.PORT;
    private Set<ClientHandler> clientThreads;
    private Thread             connectionThread;
    private Logger             log;
    private PlayerPoll         poll;
    private boolean            running;
    private ServerSocket       socket;

    // <editor-fold defaultstate="collapsed" desc="methods for server manipulation">

    /**
     *
     */
    public Server() {
        running       = false;
        clientThreads = new HashSet<ClientHandler>();
        poll          = new PlayerPoll();
        log           = Logger.getLogger("serverLog");
        log.setLevel(Level.ALL);
    }

    /**
     *
     * @throws Exception
     */
    public void startServer() throws Exception {
        log.info("Starting server");

        if ((socket != null) || (connectionThread != null)) {
            log.severe("Exception: socket or thread already exist");

            // TODO Exception
            return;
        }

        try {
            socket           = new ServerSocket(PORT);
            running          = true;
            connectionThread = new Thread(new Connector());
            connectionThread.start();
            log.info("Server started");
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            log.severe("Exception: " + ex.getMessage());

            throw new Exception();
        }
    }

    /**
     *
     */
    public void stopServer() {
        stopServer(2 * SOCKET_TO);
    }

    /**
     *
     * @param timeout
     */
    public void stopServer(long timeout) {
        log.info("Stopping server...");

        if ((socket == null) || (connectionThread == null)) {
            log.severe("Exception: no socket or thread");

            // TODO Exception
            return;
        }

        try {
            running = false;
            connectionThread.join(timeout);

            for (ClientHandler ch : clientThreads) {
                ch.sendServerDown();
                ch.join(timeout);
            }

            Thread.sleep(timeout);
            socket.close();
            connectionThread = null;
            socket           = null;
            log.info("Successfuly stopped");
        } catch (InterruptedException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            log.severe("Exception: " + ex.getMessage());
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            log.severe("Exception: " + ex.getMessage());
        }
    }

    /**
     *
     * @param client
     */
    public void addClientHandler(ClientHandler client) {
        log.info("adding client thread");
        clientThreads.add(client);
    }

    /**
     *
     * @param client
     */
    public void removeClientHandler(ClientHandler client) {
        log.info("removing client thread");
        clientThreads.remove(client);
    }

    /**
     *
     * @return
     */
    public Set<Player> getPlayerSet() {
        log.info("getPlayersSet called");

        return poll.getPlayers();
    }

    /**
     *
     * @return
     */
    public String getPlayerString() {
        log.info("getPlayersString called");

        return poll.getPlayersString();
    }

    /**
     *
     * @param player
     * @param handler
     * @throws NetworkException
     */
    public void registerPlayer(Player player, ClientHandler handler) throws NetworkException {
        log.info("registerPlayer called");
        poll.addPlayer(player, handler);
    }

    /**
     *
     * @param player
     * @throws NetworkException
     */
    public void unregisterPlayer(Player player) throws NetworkException {
        log.info("unregisterPlayer called");
        poll.removePlayer(player);
    }

    /**
     *
     * @param player
     * @return
     * @throws NetworkException
     */
    public ClientHandler getHandler(Player player) throws NetworkException {
        log.info("getHandler called");

        return poll.getHandler(player);
    }

    // </editor-fold>
    private class Connector implements Runnable {
        public void run() {
            try {
                socket.setSoTimeout(SOCKET_TO);
            } catch (SocketException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                log.severe("Exception: " + ex.getMessage());
            }

            while (running) {
                try {
                    new ClientHandler(socket.accept(), Server.this).start();
                } catch (SocketTimeoutException ex) {

                    // ok
                } catch (Exception ex) {
                    Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                    log.severe("Exception: " + ex.getMessage());

                    // TODO Exception
                }
            }
        }
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
